/***************************************************************************
 * Open Object Oriented ANSI C                                             *
 ***************************************************************************
 * Code given under the terms of GNU General Public Licence (GPLv3)        *
 * and without any waranties of any kind including but not limitative to   *
 * waranties on merchandability or any particular applications fitness.    *
 * For more information on GNU/GPL go on :                                 *
 * "http://www.gnu.org/copyleft/gpl.html"                                  *
 *                                                                         *
 * You are allowed to use it for any purposes in the terms of GNU/GPLv3    *
 * (or more).                                                              *
 * This text MUST stay in state on copyed sources (or sources using it)    *
 * and must not be altered.                                                *
 * You are allowed to add your name at the end of the Copylefs list.       *
 ***************************************************************************
 * Copylefts List :                                                        *
 ***************************************************************************
 * 2010-03-20 Patrick PIGNOL <pignol_patrick@hotmail.com> (Original)       *
 * (Type your name here if needed)                                         *
 ***************************************************************************/
#include <signal.h>
#include <stdio.h>
#include "Mem.h"
#include "ExceptionManager.h"
#include "ExceptionStateRecover.h"
#include "ExceptionHandler.h"
#include "Exception.h"

static ExceptionManagerVirtualTable* ExceptionManagerVirtualTableInstance = 0;
static ExceptionManager* ExceptionManagerInstance = 0;
static Exception* vExceptionToHandle = 0;

ExceptionManager* ExceptionManagerNew(unsigned long pArgsCount, va_list* pParameters)
{
    return ExceptionManagerGetInstance(pArgsCount, pParameters);
}

ExceptionManagerVirtualTable* ExceptionManagerInitialize(ExceptionManager* this, unsigned long pArgsCount, va_list* pParameters)
{
    if(!ExceptionManagerVirtualTableInstance)
    {
        this = ExceptionManagerGetInstance(this);
        ExceptionManagerVirtualTableInstance = (ExceptionManagerVirtualTable*) malloc(sizeof(ExceptionManagerVirtualTable));
        if(!ExceptionManagerVirtualTableInstance)
        {
            raise(SIGABRT);
        }

        ExceptionManagerVirtualTableInstance->TypeID = EXCEPTIONMANAGER_TYPEID;

        ExceptionManagerVirtualTableInstance->Interface = 0;

        ExceptionManagerVirtualTableInstance->AncestorTable = ClassInitialize((Class*)this, pArgsCount, pParameters);

        ExceptionManagerVirtualTableInstance->new = ExceptionManagerNew;
        ExceptionManagerVirtualTableInstance->Initialize = ExceptionManagerInitialize;
        ExceptionManagerVirtualTableInstance->Garbage = ((void(*)(ExceptionManager*))ExceptionManagerVirtualTableInstance->AncestorTable->Garbage);
        ExceptionManagerVirtualTableInstance->TypeOf = (unsigned long int (*)(ExceptionManager*))ExceptionManagerVirtualTableInstance->AncestorTable->TypeOf;
        ExceptionManagerVirtualTableInstance->IsSameTypeThanTypeID = (bool (*)(ExceptionManager*, unsigned long int))ExceptionManagerVirtualTableInstance->AncestorTable->IsSameTypeThanTypeID;
        ExceptionManagerVirtualTableInstance->IsKindOfTypeID = (bool (*)(ExceptionManager* this, unsigned long int))ExceptionManagerVirtualTableInstance->AncestorTable->IsKindOfTypeID;
        ExceptionManagerVirtualTableInstance->IsSameTypeThan = (bool (*)(ExceptionManager* this, Class*))ExceptionManagerVirtualTableInstance->AncestorTable->IsSameTypeThan;
        ExceptionManagerVirtualTableInstance->IsKindOf = (bool (*)(ExceptionManager* this, Class*))ExceptionManagerVirtualTableInstance->AncestorTable->IsKindOf;

    }
    ExceptionManagerVirtualTableInstance->ReferenceCount++;
    return ExceptionManagerVirtualTableInstance;
}

void ExceptionManagerConstructor(ExceptionManager* this, unsigned long int pArgsCount, va_list* pParameters)
{
    this = ExceptionManagerGetInstance();

    if(!this->aConstructed)
    {
        this->aExceptionStateRecovers = (Stack*)new((void*(*)(unsigned long int, va_list*))StackNew, 0);
        if(!this->aExceptionStateRecovers)
        {
            raise(SIGABRT);
        }
        this->aConstructed = true;
    }
}

void ExceptionManagerDestructor(ExceptionManager* this)
{
    ExceptionStateRecover* vExceptionStateRecover = 0;
    this = ExceptionManagerGetInstance();
    if(this->aConstructed)
    {
        this->aConstructed = false;
        while(this->aExceptionStateRecovers->Count(this->aExceptionStateRecovers))
        {
            vExceptionStateRecover = (ExceptionStateRecover*) this->aExceptionStateRecovers->Pop(this->aExceptionStateRecovers);
            delete((void*)vExceptionStateRecover);
        }
        delete ((void*)this->aExceptionStateRecovers);
        this->aExceptionStateRecovers = 0;
    }
}

ExceptionManager* ExceptionManagerClone(ExceptionManager* this)
{
    return ExceptionManagerGetInstance();
}

ExceptionManager* ExceptionManagerGetInstance(unsigned long pArgsCount, va_list* pParameters)
{
    ExceptionManager* this = 0;
    if(!ExceptionManagerInstance)
    {
        ExceptionManagerInstance = (ExceptionManager*)malloc(sizeof(ExceptionManager));
        this = ExceptionManagerInstance;
        this->VirtualTable = ExceptionManagerInitialize(this, pArgsCount, pParameters);
        this->Constructor = ExceptionManagerConstructor;
        this->Destructor = ExceptionManagerDestructor;
        this->Clone = ExceptionManagerClone;
        this->GetInstance = ExceptionManagerGetInstance;
        this->TryInitialize = ExceptionManagerTryInitialize;
        this->catch = ExceptionManagerCatch;
        this->try = ExceptionManagerTry;
        this->throw = ExceptionManagerThrow;
        this->Constructor(this, pArgsCount, pParameters);
    }
    return ExceptionManagerInstance;
}

void ExceptionManagerTryInitialize()
{
    ExceptionStateRecover* vExceptionStateRecover = 0;
    ExceptionManager* this = 0;
    vExceptionStateRecover = (ExceptionStateRecover*) new((void*(*)(unsigned long int, va_list*))ExceptionStateRecoverNew, 0);
    if(!vExceptionStateRecover)
    {
        raise(SIGABRT); /* error */
    }

    this = ExceptionManagerGetInstance(0, (va_list*)0);
    if(!this)
    {
        raise(SIGABRT); /* error */
    }

    this->aExceptionStateRecovers->Push(this->aExceptionStateRecovers, (Class*)vExceptionStateRecover);
}

void ExceptionManagerCatch(unsigned long int pExceptionTypeID, Class* pTargetClass, void (*pCatchingMethod)(Class*, Exception*, unsigned long int pArgsCount, va_list*))
{
    ExceptionStateRecover* vExceptionStateRecover = 0;
    ExceptionHandler* vExceptionHandler = 0;
    ExceptionManager* this = ExceptionManagerGetInstance(0,(va_list*)0);
    if(!this)
    {
        raise(SIGABRT);
    }

    vExceptionStateRecover = (ExceptionStateRecover*) this->aExceptionStateRecovers->Pop(this->aExceptionStateRecovers);
    this->aExceptionStateRecovers->Push(this->aExceptionStateRecovers, (Class*)vExceptionStateRecover);

    vExceptionHandler = (ExceptionHandler*)new ((void*(*)(unsigned long int, va_list*))ExceptionHandlerNew, 3, pExceptionTypeID, pTargetClass, pCatchingMethod);
    if(!vExceptionHandler)
    {
        raise(SIGABRT); /* error */
    }
    vExceptionStateRecover->ExceptionHandlers(vExceptionStateRecover)->Push((Stack*)vExceptionStateRecover->ExceptionHandlers(vExceptionStateRecover), (Class*)vExceptionHandler);
}

void ExceptionManagerTry(Class* pTargetClass, void (*pTryedMethod)(Class*, unsigned long int, va_list*), unsigned long int pArgsCount, ...)
{
    ExceptionStateRecover* vExceptionStateRecover = 0;
    ExceptionHandler* vExceptionHandler = 0;
    va_list vList;
    va_list vListException;
    ExceptionManager* this = ExceptionManagerGetInstance(0,(va_list*)0);

    va_start(vList, pArgsCount);
    va_start(vListException, pArgsCount);

    vExceptionStateRecover = (ExceptionStateRecover*)this->aExceptionStateRecovers->Pop(this->aExceptionStateRecovers);
    this->aExceptionStateRecovers->Push(this->aExceptionStateRecovers, (Class*)vExceptionStateRecover);

    if(setjmp(*(vExceptionStateRecover->SafeState(vExceptionStateRecover))))
    {
        /*************************************************************************************************************
         * Theoricaly throw has restored the last calling to setjmp from the good safe state for the good exception. *
         *************************************************************************************************************/
        vExceptionStateRecover = (ExceptionStateRecover*)this->aExceptionStateRecovers->Pop(this->aExceptionStateRecovers);
        this->aExceptionStateRecovers->Push(this->aExceptionStateRecovers, (Class*)vExceptionStateRecover);
        vExceptionHandler = (ExceptionHandler*)((Stack*)((ExceptionStateRecover*)vExceptionStateRecover)->ExceptionHandlers(vExceptionStateRecover))->Pop((Stack*)vExceptionStateRecover->ExceptionHandlers(vExceptionStateRecover));
        vExceptionStateRecover->ExceptionHandlers(vExceptionStateRecover)->Push((Stack*)vExceptionStateRecover->ExceptionHandlers(vExceptionStateRecover), (Class*)vExceptionHandler);
        vExceptionHandler->Handle(vExceptionHandler, vExceptionToHandle, pArgsCount, &vListException);
        delete ((void*)vExceptionToHandle);
        vExceptionToHandle = 0;
    }
    else
    {
        pTryedMethod(pTargetClass, pArgsCount, &vList);
    }
    vExceptionStateRecover = (ExceptionStateRecover*)this->aExceptionStateRecovers->Pop(this->aExceptionStateRecovers);
    delete ((void*)vExceptionStateRecover);
    vExceptionStateRecover = 0;
    vExceptionHandler = 0;
    va_end(vList);
    va_end(vListException);
}

void ExceptionManagerThrow(Exception* pException)
{
    Exception* vException = 0;
    ExceptionStateRecover* vExceptionStateRecover = 0;
    ExceptionHandler* vExceptionHandler = 0;
    ExceptionManager* this = ExceptionManagerGetInstance(0,(va_list*)0);
    while (this->aExceptionStateRecovers->Count(this->aExceptionStateRecovers))
    {
        vExceptionStateRecover = (ExceptionStateRecover*)this->aExceptionStateRecovers->Pop(this->aExceptionStateRecovers);
        while(vExceptionStateRecover->ExceptionHandlers(vExceptionStateRecover)->Count((Stack*)(vExceptionStateRecover->ExceptionHandlers(vExceptionStateRecover))))
        {
            vExceptionHandler = (ExceptionHandler*)vExceptionStateRecover->ExceptionHandlers(vExceptionStateRecover)->Pop((Stack*)vExceptionStateRecover->ExceptionHandlers(vExceptionStateRecover));
            vException = pException;
            if(vException->VirtualTable->IsKindOfTypeID(vException, vExceptionHandler->ExceptionTypeIDHandler(vExceptionHandler)))
            {
                vExceptionStateRecover->ExceptionHandlers(vExceptionStateRecover)->Push((Stack*)vExceptionStateRecover->ExceptionHandlers(vExceptionStateRecover), (Class*)vExceptionHandler);
                this->aExceptionStateRecovers->Push(this->aExceptionStateRecovers, (Class*)vExceptionStateRecover);
                vExceptionToHandle = pException;
                longjmp(*vExceptionStateRecover->SafeState(vExceptionStateRecover), 1);
            }
            delete((void*)vExceptionHandler);
        }
        delete((void*)vExceptionStateRecover);
    }
    printf("Exception not handled :\n%s\n\nApplication will Abort\nPress <Enter> to finish.",pException->Message(pException)->CharPointer((String*)pException->Message(pException), (char*)0));
    getchar();
    delete((void*)pException);
    raise(SIGABRT); /*if not enough catchs registered*/
}
